package org.maxgroundstation.groundstation.comm.service.gps;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.text.DateFormat;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
import java.util.TimeZone;

import org.maxgroundstation.groundstation.comm.service.ServiceEvent;
import org.maxgroundstation.groundstation.comm.service.ServiceListener;

/**
 * A logger for GPS data.
 * @author Scott Davis
 * 
 * Copyright (C) 2010  Scott Davis
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
public class GpsDataLogger implements ServiceListener {

    // Static members
    private String GPS_DIR = System.getProperty("user.home") + File.separator + 
            "max_groundstation" + File.separator + "gps_data";
    private static final char DEGREE_SYMBOL = '\u00B0';
    private static final String[] COMPASS_DIRECTIONS = { 
        "N", "NE", "E", "SE", "S", "SW", "W", "NW", "N" };
    
    // Data members.
    private boolean logData;
    private File logFile;
    private PrintWriter writer;
    private DateFormat dateFormatter;
    private DecimalFormat decFormatter;
    private DecimalFormat positionFormatter;

    /**
     * Constructor
     * @param gpsService the GPS service.
     */
    public GpsDataLogger(GpsService gpsService) {
        
        // Register as service listener to GPS service.
        gpsService.addServiceListener(this);
        
        // Initialize data members.
        logData = false;
        logFile = null;
        writer = null;
        dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH-mm-ssz");
        dateFormatter.setTimeZone(TimeZone.getTimeZone("UTC"));
        decFormatter = new DecimalFormat("0.0");
        positionFormatter = new DecimalFormat("0.000000");
    }
    
    /**
     * Start logging GPS data.
     * @return true if logging started successfully.
     */
    public boolean startLogging() {
        boolean success = false;
        
        // Make sure gps logging directory exists.
        File gpsDir = new File(GPS_DIR);
        if (!gpsDir.exists()) {
            gpsDir.mkdirs();
        }
        
        // Create new log file.
        String logFilename = "gps_" + dateFormatter.format(new Date()) + ".log";
        logFile = new File(GPS_DIR, logFilename);
        
        // Create writer for log file.
        try {
            writer = new PrintWriter(new FileWriter(logFile), true);
            success = true;
            logData = true;
            
            // Write column headers
            writeColumnHeaders();
        }
        catch (IOException e) {
            success = false;
            logData = false;
        }
        
        return success;
    }
    
    /**
     * Stop logging GPS data.
     */
    public void stopLogging() {
        // Close log file.
        if (writer != null) {
            writer.close();
        }
        logFile = null;
        logData = false;
    }
    
    /**
     * Checks if currently logging GPS data.
     * @return true if logging.
     */
    public boolean isLogging() {
        return logData;
    }
    
    /**
     * Gets the log file.
     * @return log file or null if none.
     */
    public File getLogFile() {
        return logFile;
    }
    
    /**
     * Writes the column headers.
     */
    private void writeColumnHeaders() {
        StringBuffer buff = new StringBuffer();
        buff.append("# ");
        buff.append("time stamp");
        buff.append("\t");
        buff.append("latitude");
        buff.append("\t");
        buff.append("longitude");
        buff.append("\t");
        buff.append("heading");
        buff.append("\t");
        buff.append("speed (m/s)");
        buff.append("\t");
        buff.append("hdop");
        
        writer.println(buff.toString());
    }
    
    @SuppressWarnings("unchecked")
    @Override
    public void serviceUpdate(ServiceEvent event) {
        if (logData) {
            if (event.getType().equals(GpsService.GPS_UPDATE)) {  
                Map<String, String> gpsData = (Map<String, String>) event.getTarget();
                StringBuffer buff = new StringBuffer();
                buff.append(gpsData.get("time"));
                buff.append("\t");
                buff.append(getFormattedLatitudeString(gpsData.get("lat")));
                buff.append("\t");
                buff.append(getFormattedLongitudeString(gpsData.get("lon")));
                buff.append("\t");
                buff.append(getFormattedHeadingString(gpsData.get("heading")));
                buff.append("\t");
                buff.append(getFormattedSpeedString(gpsData.get("speed")));
                buff.append("\t");
                buff.append(getFormattedHdopString(gpsData.get("hdop")));
                
                writer.println(buff.toString());
            }
        }
    }
    
    /**
     * Gets the formatted latitude string.
     * @param latitudeString the latitude string.
     * @return formatted string.
     */
    private String getFormattedLatitudeString(String latitudeString) {
        float latitude = Float.parseFloat(latitudeString);
        String hemisphereString = "";
        if (latitude > 0) hemisphereString = "N";
        else if (latitude < 0) hemisphereString = "S";
        String resultString = positionFormatter.format(Math.abs(latitude));
        return resultString + DEGREE_SYMBOL + hemisphereString;
    }
    
    /**
     * Gets the formatted longitude string.
     * @param latitudeString the longitude string.
     * @return formatted string.
     */
    private String getFormattedLongitudeString(String longitudeString) {
        float longitude = Float.parseFloat(longitudeString);
        String hemisphereString = "";
        if (longitude > 0) hemisphereString = "E";
        else if (longitude < 0) hemisphereString = "W";
        String resultString = positionFormatter.format(Math.abs(longitude));
        return resultString + DEGREE_SYMBOL + hemisphereString;
    }
    
    /**
     * Gets the formatted heading string.
     * @param headingString the heading string.
     * @return formatted string.
     */
    private String getFormattedHeadingString(String headingString) {
        float heading = Float.parseFloat(headingString);
        
        // Check range of heading.
        while (heading < 0) heading += 360;
        while (heading > 360) heading -= 360;
        
        // Get compass direction.
        float compassHeading = (heading / 45F) + .5F;
        if (compassHeading > 8F) compassHeading -= 8F;
        String compassDirection = COMPASS_DIRECTIONS[(int) compassHeading];
        
        String formattedHeadingString = decFormatter.format(heading);
        
        return formattedHeadingString + DEGREE_SYMBOL + compassDirection;
    }
    
    /**
     * Gets the formatted speed string.
     * @param speedString the speed string.
     * @return formatted string.
     */
    private String getFormattedSpeedString(String speedString) {
        float speed = Float.parseFloat(speedString);
        
        String formattedSpeedString = decFormatter.format(speed);
        return formattedSpeedString;
    }
    
    /**
     * Gets the formatted HDOP string.
     * @param hdopString the HDOP string.
     * @return formatted string.
     */
    private String getFormattedHdopString(String hdopString) {
        float hdop = Float.parseFloat(hdopString);
        
        String formattedHdopString = decFormatter.format(hdop);
        return formattedHdopString;
    }
}